﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using Jayden.Dll.Core.Enums;
using System.IO;

namespace Jayden.Dll.Core
{
	public class RealNumber : ICloneable, IDisposable, IEquatable<RealNumber>, IComparable<RealNumber>
	{
		public class RealNumberConfiguration
		{
			private int m_MaximumDigits = 2048;
			private int m_MinimumExponent = -50;
			private bool m_CheckRepeatingDigits = true;

			public RealNumberConfiguration(int maximumDigits = 2048, int minimumExponent = -50, bool checkRepeatingDigits = true)
			{
				m_MinimumExponent = minimumExponent;
				m_MaximumDigits = maximumDigits;
				m_CheckRepeatingDigits = checkRepeatingDigits;
			}

			public int MaximumDigits { get { return m_MaximumDigits; } }
			public int MinimumExponent { get { return m_MinimumExponent; } }
			public bool CheckRepeatingDigits { get { return m_CheckRepeatingDigits; } }
		}

		private bool m_IsPositive = true;
		private byte[] m_Digits;
		private int m_Exponent;
		private int m_PeriodLength = 0;
		private RealNumber m_Numerator;
		private RealNumber m_Denominator;

		public static readonly RealNumber Zero = new RealNumber();
		public static readonly RealNumber One = 1;

		public static byte[] ReadFromText(string digits)
		{
			byte[] b = new byte[digits.Length];
			for (int c = 0; c < digits.Length; c++)
			{
				b[c] = (byte)(digits[c] - '0');
				if (b[c] > 9)
					throw new FormatException();
			}
			return b;
		}
		public static byte[] ReadFromTextFile(string filename)
		{
			string line = File.ReadAllText(filename);
			return ReadFromText(line);
		}
		public static byte[] ReadFromDataFile(string filename) { return File.ReadAllBytes(filename); }
		public static void SaveToDataFile(string filename, byte[] digits) { File.WriteAllBytes(filename, digits); }
		public static void ConvertTextFileToDataFile(string textFile, string dataFile)
		{
			byte[] digits = ReadFromTextFile(textFile);
			SaveToDataFile(dataFile, digits);
		}
		
		public static RealNumber Factorial(ulong value)
		{
			if (value == 0)
				return One;
			if(value < 3)
				return new RealNumber(value);
			ulong current = 3;
			RealNumber result = new RealNumber(6);
			while (current < value)
			{
				current++;
				RealNumber mul = new RealNumber(current);
				result = result.Multiply(mul);
			}
			return result;
		}
		public static RealNumber Parse(string value) { return new RealNumber(value, CultureInfo.CurrentCulture); }
		public static RealNumber Parse(string value, CultureInfo info) { return new RealNumber(value, info); }
		public static bool TryParse(string value, CultureInfo info, out RealNumber number)
		{
			try
			{
				number = Parse(value, info);
				return true;
			}
			catch
			{
				number = null;
				return false;
			}
		}
		public static RealNumber Pow(RealNumber x, ulong y)
		{
			if (y == 0)
				return One;
			RealNumber mul = x.Clone();
			for (ulong c = 1; c < y; c++)
				x = x.Multiply(mul);
			return x;
		}
		public static RealNumber Abs(RealNumber value)
		{
			if (value.m_IsPositive)
				return value;
			RealNumber abs = value.Clone();
			abs.m_IsPositive = true;
			if (abs.m_Numerator != null)
				abs.m_Numerator.m_IsPositive = true;
			return abs;
		}
		public static RealNumber Ceiling(RealNumber value)
		{
			if (value.m_Exponent >= 0)
				return value;
			RealNumber integer = value.Cut(value.m_Digits.Length + value.m_Exponent);
			if (integer < value)
				integer += 1;
			return integer;
		}
		public static RealNumber Floor(RealNumber value)
		{
			if (value.m_Exponent >= 0)
				return value;
			RealNumber integer = value.Cut(value.m_Digits.Length + value.m_Exponent);
			if (integer > value)
				integer -= 1;
			return integer;
		}
		public static RealNumber Round(RealNumber value, int decimals, MidpointRounding rounding)
		{
			int vDecimals = -value.m_Exponent;
			if (decimals >= vDecimals)
				return value;
			int cut = vDecimals - decimals;
			RealNumber round = value.Cut(value.m_Digits.Length - cut);
			RealNumber diff = Abs(value - round);
			diff.m_Exponent += decimals;
			int compare = diff.CompareTo(0.5);
			round.m_Exponent += decimals;
			if (compare > 0)
				round += (value.IsPositive ? 1 : -1);
			else if (compare == 0)
			{
				if(rounding == MidpointRounding.AwayFromZero)
					round += (value.IsPositive ? 1 : -1);
				else if (rounding == MidpointRounding.ToEven)
				{
					byte b = round.m_Digits[round.m_Digits.Length - 1];
					if((b & 1) != 0)
						round += (value.IsPositive ? 1 : -1);
				}
			}
			round.m_Exponent -= decimals;
			return round;
		}
		public static RealNumber Sign(RealNumber value) { return value.CompareTo(0); }
		public static RealNumber Min(params RealNumber[] values)
		{
			RealNumber lowest = null;
			for (int c = 0; c < values.Length; c++)
				if (lowest == null || lowest.CompareTo(values[c]) > 0)
					lowest = values[c];
			return lowest;
		}
		public static RealNumber Max(params RealNumber[] values)
		{
			RealNumber lowest = null;
			for (int c = 0; c < values.Length; c++)
				if (lowest == null || lowest.CompareTo(values[c]) < 0)
					lowest = values[c];
			return lowest;
		}
		public static RealNumber Sqrt(RealNumber value, int precision = 50)
		{
			int current = value.HighestDigitExponent;
			if ((current % 2) == 0)
				current++;
			bool finished = false;
			RealNumber p = new RealNumber();
			List<byte> result = new List<byte>();
			RealNumber c = new RealNumber();
			int exponent = int.MaxValue;
			while(!finished)
			{
				RealNumber remainder = new RealNumber(true, 0, 0, value[current], value[current - 1]);
				c = c.Add(remainder);
				byte x = 0;
				RealNumber twentyP = p.Multiply(2);
				twentyP.m_Exponent++;
				while (twentyP.Add(x + 1).Multiply(x + 1).CompareTo(c) <= 0)
					x++;
				RealNumber y = twentyP.Add(x).Multiply(x);
				result.Add(x);
				if (current == 1)
					exponent = result.Count;
				if (result.Count == precision)
					finished = true;
				p.m_Exponent++;
				p = p.Add(x);
				c = c.Subtract(y);
				c.m_Exponent += 2;
				if (c.IsZero && value.LowestDigitExponent > current)
					finished = true;
				current -= 2;
			}
			if (exponent == int.MaxValue)
				exponent = (current / 2);
			return new RealNumber(true, exponent - result.Count, 0, result.ToArray());
		}
		public static RealNumber Gcd(RealNumber a, RealNumber b)
		{
			int compare = a.CompareTo(b);
			while (compare != 0)
			{
				if (compare > 0)
					a = a - b;
				else
					b = b - a;
				compare = a.CompareTo(b);
			}
			return a;
		}
		public static RealNumber Sin(RealNumber value, int recursion = 50)
		{
			RealNumber x = value;
			RealNumber xPower = value;
			RealNumber xFact = 1;
			ulong factValue = 2;
			bool sign = false;
			for (int c = 1; c < recursion; c++)
			{
				RealNumber xFactMul = factValue;
				factValue++;
				xFactMul *= factValue;
				factValue++;
				xFact *= xFactMul;
				xPower *= value * value;
				RealNumber term = xPower / xFact;
				term.SetSign(sign);
				sign = !sign;
				x += term;
			}
			return x;
		}
		public static RealNumber Cos(RealNumber value, int recursion = 50)
		{
			RealNumber x = 1;
			RealNumber xPower = 1;
			RealNumber xFact = 1;
			ulong factValue = 1;
			bool sign = false;
			for (int c = 1; c < recursion; c++)
			{
				RealNumber xFactMul = factValue;
				factValue++;
				xFactMul *= factValue;
				factValue++;
				xFact *= xFactMul;
				xPower *= value * value;
				RealNumber term = xPower / xFact;
				term.SetSign(sign);
				sign = !sign;
				x += term;
			}
			return x;
		}
		public static RealNumber Tan(RealNumber value, int recursion = 50)
		{
			RealNumber sin = Sin(value, recursion);
			RealNumber cos = Cos(value, recursion);
			return sin / cos;
		}

		private static RealNumber m_Pi = null;
		private static RealNumber m_E = null;

		public static RealNumber Pi(int digitsCount)
		{
			if (m_Pi == null)
			{
				if (File.Exists("pi.dat"))
				{
					byte[] digits = ReadFromDataFile("pi.dat");
					m_Pi = new RealNumber(true, -(digits.Length - 1), 0, digits);
				}
			}
			if (m_Pi != null)
				return m_Pi.Cut(digitsCount);
			throw new MissingMemberException("m_Pi is null, 'pi.dat' has been found.");
		}
		public static RealNumber E(int digitsCount)
		{
			if (m_E == null)
			{
				if (File.Exists("pi.dat"))
				{
					byte[] digits = ReadFromDataFile("e.dat");
					m_E = new RealNumber(true, -(digits.Length - 1), 0, digits);
				}
			}
			if (m_E != null)
				return m_E.Cut(digitsCount);
			throw new MissingMemberException("m_E is null, 'e.dat' has been found.");
		}

		public static RealNumber ComputePiBbp(int recursion)
		{
			RealNumber p16 = 1;
			RealNumber pi = 0;
			RealNumber four = 4;
			RealNumber two = 2;
			RealNumber sixteen = 16;
			double piCheck = 0;
			double p16Check = 1;
			for (int k = 0; k < recursion; k++)
			{
				RealNumber k8 = k * 8;
				RealNumber mul = 1 / p16;
				RealNumber term1 = four / (k8 + 1);
				RealNumber term2 = two / (k8 + 4);
				RealNumber term3 = One / (k8 + 5);
				RealNumber term4 = One / (k8 + 6);
				pi += mul * (term1 - term2 - term3 - term4);
#if DEBUG
				double mulCheck = 1.0 / p16Check;
				double term1Check = 4.0 / ((k * 8) + 1);
				double term2Check = 2.0 / ((k * 8) + 4);
				double term3Check = 1.0 / ((k * 8) + 5);
				double term4Check = 1.0 / ((k * 8) + 6);
				piCheck += mulCheck * (term1Check - term2Check - term3Check - term4Check);
				RealNumber test = piCheck - pi;
				if (test.HighestDigitExponent > -15)
					throw new Exception();
				p16Check *= 16;
#endif
				p16 *= sixteen;
			}
			return pi;
		}
		public static RealNumber ComputePiLeibniz(int recursion)
		{
			RealNumber one = 1;
			RealNumber pi = one;
			bool isPositive = false;
			for (int c = 1; c < recursion; c++)
			{
				RealNumber denominator = new RealNumber(2 * c + 1);
				RealNumber term = one / denominator;
				if (isPositive)
					pi += term;
				else
					pi -= term;
				isPositive = !isPositive;
			}

			return pi * 4;
		}

		public static RealNumber operator +(RealNumber a, RealNumber b) { return a.Add(b); }
		public static RealNumber operator -(RealNumber a, RealNumber b) { return a.Subtract(b); }
		public static RealNumber operator *(RealNumber a, RealNumber b) { return a.Multiply(b); }
		public static RealNumber operator /(RealNumber a, RealNumber b) { return a.Divide(b); }

		public static bool operator ==(RealNumber a, RealNumber b)
		{
			if((object)a == null && (object)b == null)
				return true;
			if ((object)a == null || (object)b == null)
				return false;
			return a.Equals(b);
		}
		public static bool operator <(RealNumber a, RealNumber b) { return a.CompareTo(b) < 0; }
		public static bool operator >(RealNumber a, RealNumber b) { return a.CompareTo(b) > 0; }
		public static bool operator <=(RealNumber a, RealNumber b) { return a.CompareTo(b) <= 0; }
		public static bool operator >=(RealNumber a, RealNumber b) { return a.CompareTo(b) >= 0; }
		public static bool operator !=(RealNumber a, RealNumber b) { return !(a == b); }

		public static implicit operator RealNumber(double d) { return new RealNumber(d); }
		public static implicit operator RealNumber(float f) { return new RealNumber(f); }
		public static implicit operator RealNumber(decimal d) { return new RealNumber(d); }
		public static implicit operator RealNumber(ulong ul) { return new RealNumber(ul); }
		public static implicit operator RealNumber(long l) { return new RealNumber(l); }
		public static implicit operator RealNumber(uint ui) { return new RealNumber((ulong)ui); }
		public static implicit operator RealNumber(int i) { return new RealNumber((long)i); }
		public static implicit operator RealNumber(ushort us) { return new RealNumber((ulong)us); }
		public static implicit operator RealNumber(short s) { return new RealNumber((long)s); }
		public static implicit operator RealNumber(byte b) { return new RealNumber((ulong)b); }

		public static explicit operator RealNumber(string s) { return new RealNumber(s, CultureInfo.InvariantCulture); }
		public static explicit operator double(RealNumber r) { return double.Parse(r.ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); }
		public static explicit operator float(RealNumber r) { return float.Parse(r.ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); }
		public static explicit operator decimal(RealNumber r) { return decimal.Parse(r.ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); }
		public static explicit operator ulong(RealNumber r) { return ulong.Parse(r.ToInteger().ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); }
		public static explicit operator long(RealNumber r) { return long.Parse(r.ToInteger().ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); }
		public static explicit operator uint(RealNumber r) { return (uint)ulong.Parse(r.ToInteger().ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); }
		public static explicit operator int(RealNumber r) { return (int)long.Parse(r.ToInteger().ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); }
		public static explicit operator ushort(RealNumber r) { return (ushort)ulong.Parse(r.ToInteger().ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); }
		public static explicit operator short(RealNumber r) { return (short)long.Parse(r.ToInteger().ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); }
		public static explicit operator byte(RealNumber r) { return (byte)ulong.Parse(r.ToInteger().ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); }

		private RealNumber Cut(int digits)
		{
			if(m_Digits == null || m_Digits.Length < digits)
				return this;
			if (digits == 0)
				return Zero;
			byte[] newDigits = new byte[digits];
			for (int c = 0; c < digits; c++)
				newDigits[c] = m_Digits[c];
			return new RealNumber(m_IsPositive, m_Exponent + (m_Digits.Length - digits), 0, newDigits);
		}

		public RealNumber() { }
		public RealNumber(bool positive, int exponent, int periodLength, params byte[] digits)
		{
			m_IsPositive = positive;
			m_Exponent = exponent;
			m_PeriodLength = periodLength;
			if(digits != null && digits.Length > 0)
				m_Digits = digits.Clone() as byte[];

			Trim();
			if(m_PeriodLength != 0)
			{
				RealNumber a = null, b = null;
				RealNumber w = ToFraction(ref a, ref b);
				m_Numerator = a + (w * b);
				m_Denominator = b;
			}
		}
		public RealNumber(ulong value) { SetDigitsFrom(value); }
		public RealNumber(long value)
		{
			if (value < 0)
			{
				m_IsPositive = false;
				value = -value;
			}
			SetDigitsFrom((ulong)value);
		}
		public RealNumber(double value) : this(value.ToString("0.#######################", CultureInfo.InvariantCulture), CultureInfo.InvariantCulture) { }
		public RealNumber(decimal value) : this(value.ToString("0.#################################", CultureInfo.InvariantCulture), CultureInfo.InvariantCulture) { }
		public RealNumber(string value, CultureInfo info) : this(value, info.NumberFormat.NumberDecimalSeparator, info.NumberFormat.NegativeSign) { }
		public RealNumber(string value, string decimalSeparator, string negativeSign)
		{
			int index = 0;
			value = value.Trim();
			if (value.StartsWith(negativeSign))
			{
				m_IsPositive = false;
				index+=negativeSign.Length;
			}
			List<byte> digits = new List<byte>();
			int coma = -1;
			for (; index < value.Length; index++)
			{
				if (char.IsDigit(value[index]))
					digits.Add((byte)(value[index] - '0'));
				else if (value[index] == decimalSeparator[0] && coma == -1)
				{
					index += (decimalSeparator.Length - 1);
					coma = digits.Count;
				}
				else
					throw new FormatException();
			}
			if (coma != -1)
				m_Exponent = coma - digits.Count;

			m_Digits = digits.ToArray();
			digits = null;
			Trim();
		}

		private void SetDigitsFrom(ulong value)
		{
			m_Exponent = 0;
			m_Digits = null;
			List<byte> digits = new List<byte>();
			while (value > 0)
			{
				byte digit = (byte)(value % 10);
				value /= 10;
				digits.Insert(0, digit);
			}
			if (digits.Count > 0)
			{
				m_Digits = digits.ToArray();
				Trim();
			}
		}
		private void SetSign(bool sign)
		{
			if (m_IsPositive == sign)
				return;
			m_IsPositive = sign;
			if (m_Numerator != null)
				m_Numerator.m_IsPositive = sign;
		}

		public bool IsInteger { get { return m_Exponent >= 0 && !IsRepeating; } }
		public bool IsZero
		{
			get
			{
				return m_Digits == null || (m_Digits.Length == 1 && m_Digits[0] == 0);
			}
		}
		public bool IsOne
		{
			get
			{
				return m_Digits != null && m_Digits.Length == 1 && m_Digits[0] == 1 && m_Exponent == 0 && m_PeriodLength == 0 && m_IsPositive;
			}
		}

		private RealNumber ToInteger() { return Round(this, 0, MidpointRounding.AwayFromZero); }

		public bool IsPositive { get { return m_Digits == null ? true : m_IsPositive; } }
		public bool IsRepeating { get { return !IsZero && m_PeriodLength > 0; } }

		public int NumberOfDigits { get { return m_Digits == null ? 0 : m_Digits.Length; } }
		public int NumberOfDecimals { get { return -Math.Min(0, LowestDigitExponent); } }
		public int HighestDigitExponent { get { return m_Digits == null ? 0 : (m_Digits.Length - 1) + m_Exponent; } }
		public int LowestDigitExponent { get { return m_Digits == null ? 0 : m_Exponent; } }

		public byte this[int index]
		{
			get
			{
				if(m_Digits == null)
					return 0;
				index = HighestDigitExponent - index;
				if (m_PeriodLength > 0 && index >= m_Digits.Length)
				{
					int fakeIndex = ((index - m_Digits.Length) % m_PeriodLength) + (m_Digits.Length - m_PeriodLength);
					return m_Digits[fakeIndex];
				}
				if (index < 0 || index >= m_Digits.Length)
					return 0;
				return m_Digits[index];
			}
		}

		public override string ToString() { return ToString(CultureInfo.CurrentCulture); }
		public string ToString(CultureInfo info) { return ToString(info.NumberFormat.NumberDecimalSeparator, info.NumberFormat.NegativeSign, RepeatingDecimalNotationType.Ellipsis); }
		private string ToString(string decimalSepataror, string negativeSign, RepeatingDecimalNotationType repeatingType)
		{
			if (IsZero)
				return "0";
			StringBuilder builder = new StringBuilder();
			if (!m_IsPositive)
				builder.Append(negativeSign);
			int exponent = m_Exponent;
			if(-exponent >= m_Digits.Length)
				builder.Append("0" + decimalSepataror);
			int zeroes = -(m_Digits.Length + exponent);
			for (int c = 0; c < zeroes;c++)
				builder.Append('0');
			zeroes = -zeroes;
			for (int index = 0; index < m_Digits.Length; index++)
			{
				if (zeroes != 0 && index == zeroes)
					builder.Append(decimalSepataror);
				builder.Append((char)('0' + m_Digits[index]));
			}
			if (m_PeriodLength > 0)
			{
				int length = 1;
				if(m_Exponent >= 0)
					length = 1 + (m_Exponent / m_PeriodLength);
				int comaIndex = m_Exponent;
				for (int l = 0; l < length; l++)
					for (int c = 0; c < m_PeriodLength; c++)
					{
						if (comaIndex == 0)
							builder.Append(decimalSepataror);
						comaIndex--;
						builder.Append((char)('0' + m_Digits[m_Digits.Length - m_PeriodLength + c]));
					}
				builder.Append("...");
			}
			else
			{
				for (int c = 0; c < m_Exponent; c++)
					builder.Append('0');
			}
			return builder.ToString();
		}

		public void Trim()
		{
			if(m_Digits == null)
				return;
			if (m_Digits.Length == m_PeriodLength)
				return;
			int start = 0;
			while (start < m_Digits.Length && m_Digits[start] == 0)
				start++;
			int end = m_Digits.Length - 1;
			if (m_PeriodLength == 0)
			{
				while (start <= end && m_Digits[end] == 0)
					end--;
			}
			int newLength = (end - start) + 1;
			if (newLength > 0 && newLength < m_PeriodLength)
			{
				start -= (m_PeriodLength - newLength);
				newLength = m_PeriodLength;
			}
			if (newLength == 0)
			{
				m_Digits = null;
				m_IsPositive = true;
				m_Exponent = 0;
				m_PeriodLength = 0;
			}
			else if(newLength != m_Digits.Length)
			{
				byte[] newDigits = new byte[newLength];
				for (int c = start; c <= end; c++)
					newDigits[c - start] = m_Digits[c];
				m_Exponent += m_Digits.Length - (end + 1);
				m_Digits = newDigits;
			}
		}

		public int CompareTo(RealNumber other)
		{
			if (object.ReferenceEquals(this, other))
				return 0;
			if (IsZero && other.IsZero)
				return 0;
			if (IsZero)
				return other.m_IsPositive ? -1 : 1;
			if (other.IsZero)
				return m_IsPositive ? 1 : -1;
			if (m_IsPositive && !other.m_IsPositive)
				return 1;
			if (!m_IsPositive && other.m_IsPositive)
				return -1;
			if (m_IsPositive)
				return CompareWithoutSign(other);
			else
				return -CompareWithoutSign(other);
		}
		private int CompareWithoutSign(RealNumber other)
		{
			if (HighestDigitExponent > other.HighestDigitExponent)
				return 1;
			if (HighestDigitExponent < other.HighestDigitExponent)
				return -1;
			int compare = CompareDigits(other.m_Digits);
			if (compare != 0)
				return compare;
			if (m_PeriodLength == other.m_PeriodLength)
				return compare;
			if (m_PeriodLength > 0 && other.m_PeriodLength == 0)
				return 1;
			if (m_PeriodLength == 0 && other.m_PeriodLength > 0)
				return -1;
			throw new NotImplementedException();
		}
		private int CompareDigits(byte[] digits)
		{
			return CompareDigits(m_Digits, digits);
		}
		private static int CompareDigits(byte[] a, byte[] b)
		{
			for (int c = 0; c < a.Length && c < b.Length; c++)
				if (a[c] > b[c])
					return 1;
				else if (a[c] < b[c])
					return -1;
			return a.Length.CompareTo(b.Length);
		}
		private static bool EqualDigits(byte[] a, byte[] b)
		{
			if (a.Length != b.Length)
				return false;
			return CompareDigits(a, b) == 0;
		}

		public bool Equals(RealNumber other) { return CompareTo(other) == 0; }
		public override bool Equals(object obj)
		{
			if(obj is RealNumber)
				return CompareTo(obj as RealNumber) == 0;
			return false;
		}
		public override int GetHashCode()
		{
			int hashCode = 0;
			hashCode += m_IsPositive.GetHashCode();
			hashCode *= 31;
			hashCode += m_Exponent.GetHashCode();
			if(m_Digits != null)
			{
				for (int c = 0; c < m_Digits.Length; c++)
				{
					hashCode *= 31;
					hashCode += m_Digits[c].GetHashCode();
				}
			}
			return hashCode;
		}

		public void Dispose() { m_Digits = null; }

		public RealNumber Clone() { return new RealNumber(m_IsPositive, m_Exponent, m_PeriodLength, m_Digits); }
		object ICloneable.Clone() { return new RealNumber(m_IsPositive, m_Exponent, m_PeriodLength, m_Digits); }

		public RealNumber Add(RealNumber other)
		{
			if (IsZero)
				return other.Clone();
			if (other.IsZero)
				return Clone();
			if (m_IsPositive == other.m_IsPositive)
			{
				if (other.IsRepeating || IsRepeating)
				{
					if (IsRepeating && other.IsRepeating)
					{
						RealNumber numThis = null, divThis = null;
						RealNumber integerThis = this.ToFraction(ref numThis, ref divThis);
						RealNumber numOther = null, divOther = null;
						RealNumber integerOther = other.ToFraction(ref numOther, ref divOther);
						return (integerThis + integerOther) + (((numThis * divOther) + (numOther * divThis)) / (divThis * divOther));
					}
					else if (IsRepeating)
					{
						RealNumber num = null, div = null;
						RealNumber integer = this.ToFraction(ref num, ref div);
						return (num + ((integer + other) * div)) / div;
					}
					if (other.IsRepeating)
					{
						RealNumber num = null, div = null;
						RealNumber integer = other.ToFraction(ref num, ref div);
						return (((this + integer) * div) + num) / div;
					}
				}
				int highest = Math.Max(HighestDigitExponent, other.HighestDigitExponent);
				int lowest = Math.Min(LowestDigitExponent, other.LowestDigitExponent);
				int newLength = (highest - lowest) + 2;
				byte report = 0;
				byte[] digits = new byte[newLength];
				for (int c = newLength - 1; c >= 1; c--)
				{
					byte a = this[highest - c + 1];
					byte b = other[highest - c + 1];
					byte sum = (byte)(a + b + report);
					if (sum > 9)
					{
						report = 1;
						sum = (byte)(sum - 10);
					}
					else
						report = 0;
					digits[c] = sum;
				}
				digits[0] = report;
				return new RealNumber(m_IsPositive, lowest, 0, digits);
			}
			else
			{
				using (RealNumber other2 = new RealNumber(m_IsPositive, other.m_Exponent, other.m_PeriodLength, other.m_Digits))
				{
					return Subtract(other2);
				}
			}
		}
		public RealNumber Subtract(RealNumber other)
		{
			if (IsZero)
				return new RealNumber(!other.m_IsPositive, other.m_Exponent, 0, other.m_Digits);
			if (other.IsZero)
				return Clone();
			if (m_IsPositive == other.m_IsPositive)
			{
				int compare = CompareWithoutSign(other);
				if(compare == 0)
					return Zero;
				if (compare > 0)
				{
					if (other.IsRepeating || IsRepeating)
					{
						if (IsRepeating && other.IsRepeating)
						{
							RealNumber numThis = null, divThis = null;
							RealNumber integerThis = this.ToFraction(ref numThis, ref divThis);
							RealNumber numOther = null, divOther = null;
							RealNumber integerOther = other.ToFraction(ref numOther, ref divOther);
							return (integerThis - integerOther) + (((numThis * divOther) - (numOther * divThis)) / (divThis * divOther));
						}
						else if (IsRepeating)
						{
							RealNumber num = null, div = null;
							RealNumber integer = this.ToFraction(ref num, ref div);
							return (((integer - other) * div) + num) / div;
						}
						if (other.IsRepeating)
						{
							RealNumber num = null, div = null;
							RealNumber integer = other.ToFraction(ref num, ref div);
							return (((this - integer) * div) - num) / div;
						}
					}
					int highest = Math.Max(HighestDigitExponent, other.HighestDigitExponent);
					int lowest = Math.Min(LowestDigitExponent, other.LowestDigitExponent);
					int newLength = (highest - lowest) + 1;
					byte report = 0;
					byte[] digits = new byte[newLength];
					for (int c = newLength - 1; c >= 0; c--)
					{
						byte a = this[highest - c];
						byte b = other[highest - c];
						byte dif = 0;
						if (a < (b + report))
						{
							dif = (byte)((a + 10) - (b + report));
							report = 1;
						}
						else
						{
							dif = (byte)(a - (b + report));
							report = 0;
						}
						digits[c] = dif;
					}
					if (report != 0)
						throw new Exception();
					return new RealNumber(m_IsPositive, lowest, 0, digits);
				}
				else
				{
					RealNumber result = other.Subtract(this);
					result.m_IsPositive = !result.m_IsPositive;
					if (result.m_Numerator != null)
						result.m_Numerator.m_IsPositive = result.m_IsPositive;
					return result;
				}
			}
			else
			{
				using (RealNumber other2 = new RealNumber(m_IsPositive, other.m_Exponent, other.m_PeriodLength, other.m_Digits))
				{
					return Add(other2);
				}
			}
		}
		public RealNumber Multiply(RealNumber other)
		{
			if (IsZero || other.IsZero)
				return Zero;
			if (IsOne)
				return other.Clone();
			if (other.IsOne)
				return Clone();
			if (other.IsRepeating || IsRepeating)
			{
				if (IsRepeating && other.IsRepeating)
				{
					RealNumber numThis = null, divThis = null;
					RealNumber integerThis = this.ToFraction(ref numThis, ref divThis);
					RealNumber numOther = null, divOther = null;
					RealNumber integerOther = other.ToFraction(ref numOther, ref divOther);
					return ((numThis + (integerThis * divThis)) * (numOther + (integerOther * divOther))) / (divThis * divOther);
				}
				else if (IsRepeating)
				{
					RealNumber num = null, div = null;
					RealNumber integer = this.ToFraction(ref num, ref div);
					return (other * ((integer * div) + num)) / div;
				}
				if (other.IsRepeating)
				{
					RealNumber num = null, div = null;
					RealNumber integer = other.ToFraction(ref num, ref div);
					return (this * ((integer * div) + num)) / div;
				}
			}
			byte[] digits = new byte[other.m_Digits.Length + 1];
			RealNumber realResult = null;
			int exponent = 0;
			for (int index = m_Digits.Length - 1; index >= 0; index--)
			{
				byte a = m_Digits[index];
				byte report = 0;
				
				for (int index2 = other.m_Digits.Length - 1; index2 >= 0; index2--)
				{
					byte b = other.m_Digits[index2];
					byte result = (byte)(report + (a * b));
					report = (byte)(result / 10);
					result = (byte)(result % 10);
					digits[index2 + 1] = result;
				}
				if (report > 0)
					digits[0] = report;
				else
					digits[0] = 0;

				RealNumber part = new RealNumber(true, m_Exponent + other.m_Exponent + exponent, 0, digits);
				if (realResult != null)
					realResult = realResult.Add(part);
				else
					realResult = part;

				exponent++;
			}
			realResult.m_IsPositive = (m_IsPositive == other.m_IsPositive);
			if (realResult.m_Numerator != null)
				realResult.m_Numerator.m_IsPositive = realResult.m_IsPositive;
			return realResult;
		}
		public RealNumber Divide(RealNumber other)
		{
			if (other.IsRepeating || IsRepeating)
				throw new NotImplementedException();
			if (IsZero)
				return Zero;
			if (other.IsOne)
				return Clone();
			if (CompareTo(other) == 0)
				return One;
			int periodLength = 0;
			byte[] digits = DivideDigits(other.m_Digits, out periodLength);
			RealNumber result = new RealNumber(m_IsPositive == other.m_IsPositive, HighestDigitExponent - other.HighestDigitExponent - (digits.Length - 1), periodLength, digits);
			return result;
		}

		private byte[] DivideDigits(byte[] digits, out int periodLength)
		{
			periodLength = 0;
			int countDico = 0;
			DigitsDictionary dictionary = new DigitsDictionary();
			List<byte> result = new List<byte>();
			List<byte[]> digitsTested = new List<byte[]>();
			RealNumber a = new RealNumber();
			RealNumber b = new RealNumber();
			a.m_Digits = m_Digits;
			b.m_Digits = digits;
			a.m_Exponent = -m_Digits.Length;
			b.m_Exponent = -digits.Length;
			int compare;
			do
			{
				compare = a.CompareTo(b);
				if (compare == 0)
					result.Add(1);
				else if (compare > 0)
				{
					int periodIndexFromDico = -1;
					if (dictionary.TryAdd(a.m_Digits, countDico, out periodIndexFromDico))
						countDico++;
					//for (int periodIndex = 0; periodIndex < digitsTested.Count; periodIndex++)
					else
					{
						int periodIndex = periodIndexFromDico;
						byte[] period = digitsTested[periodIndex];
						//if (period == null)
						//	continue;
						//if (EqualDigits(a.m_Digits, period))
						{
							periodLength = (result.Count - periodIndex);
							while (result.Count > periodLength && result[result.Count - 1] == 0 && result[result.Count - periodLength - 1] == 0)
							{
								result.RemoveAt(result.Count - 1);
								//result.Insert(0, 0);
							}
							return result.ToArray();
						}
					}
					digitsTested.Add(null/*a.m_Digits*/);
					RealNumber increment = b;
					RealNumber previous = b;
					RealNumber value = b.Add(increment);
					compare = a.CompareTo(value);
					byte count = 1;
					while (compare > 0)
					{
						count++;
						previous = value;
						value = value.Add(increment);
						compare = a.CompareTo(value);
					}
					a = a.Subtract(previous);
					if (compare == 0)
						count++;
					a.m_Exponent++;
					result.Add(count);
				}
				else if (compare < 0)
				{
					digitsTested.Add(null);
					a.m_Exponent++;
					result.Add(0);
					countDico++;
				}
				if (result.Count >= 2048)
					return result.ToArray();
			} while (compare != 0);
			return result.ToArray();
		}

		private RealNumber ToFraction(ref RealNumber numerator, ref RealNumber denominator)
		{
			if (m_Numerator != null && m_Denominator != null)
			{
				numerator = m_Numerator;
				denominator = m_Denominator;
				return Zero;
			}
			if (IsRepeating)
			{
				byte[] repeatingDigits = new byte[m_PeriodLength];
				for (int c = 0; c < m_PeriodLength; c++)
					repeatingDigits[c] = m_Digits[m_Digits.Length - m_PeriodLength + c];
				RepeatingToFraction(repeatingDigits, ref numerator, ref denominator);
				int expChange = (m_Exponent + m_PeriodLength);
				if (expChange > 0)
				{
					numerator = numerator.Clone();
					numerator.m_Exponent += expChange;
				}
				else if(expChange < 0)
				{
					denominator = denominator.Clone();
					denominator.m_Exponent -= expChange;
				}
				if (m_PeriodLength == m_Digits.Length)
					return Zero;
				byte[] nonRepeatingDigits = new byte[m_Digits.Length - m_PeriodLength];
				for (int c = 0; c < nonRepeatingDigits.Length; c++)
					nonRepeatingDigits[c] = m_Digits[c];
				numerator.m_IsPositive = m_IsPositive;
				return new RealNumber(m_IsPositive, m_Exponent + m_PeriodLength, 0, nonRepeatingDigits);
			}
			else
			{
				numerator = Zero;
				denominator = One;
				return this;
			}
		}

		private static void RepeatingToFraction(byte[] repeatingDigits, ref RealNumber numerator, ref RealNumber denominator)
		{
			numerator = new RealNumber(true, 0, 0, repeatingDigits);
			denominator = Pow(10, (ulong)repeatingDigits.Length).Subtract(1);

			RealNumber a = Gcd(numerator, denominator);

			if (a != 1)
			{
				numerator = numerator.Divide(a);
				denominator = denominator.Divide(a);
			}
		}
	}

	public class DigitsDictionary
	{
		private bool m_Digits = false;
		private int m_Index = -1;
		private DigitsDictionary[] m_SubDictionary = null;

		public bool Contains(byte[] digits, int digitIndex, ref int index)
		{
			index = -1;
			if (digitIndex == digits.Length)
			{
				index = m_Index;
				return m_Digits;
			}
			if (m_SubDictionary == null)
				return false;
			DigitsDictionary sub = m_SubDictionary[digits[digitIndex]];
			if (sub == null)
				return false;
			return sub.Contains(digits, digitIndex + 1, ref index);
		}
		public void Add(byte[] digits, int digitIndex, int index)
		{
			if (digits.Length == digitIndex)
			{
				m_Index = index;
				m_Digits = true;
			}
			else
			{
				if (m_SubDictionary == null)
					m_SubDictionary = new DigitsDictionary[10];
				byte digit = digits[digitIndex];
				if (m_SubDictionary[digit] == null)
					m_SubDictionary[digit] = new DigitsDictionary();
				m_SubDictionary[digit].Add(digits, digitIndex + 1, index);
			}
		}
		public bool TryAdd(byte[] digits, int digitIndex, int index, out int currentIndex)
		{
			if (digits.Length == digitIndex)
			{
				if (m_Digits)
				{
					currentIndex = m_Index;
					return false;
				}
				else
				{
					currentIndex = -1;
					m_Index = index;
					m_Digits = true;
					return true;
				}
			}
			else
			{
				if (m_SubDictionary == null)
					m_SubDictionary = new DigitsDictionary[10];
				byte digit = digits[digitIndex];
				if (m_SubDictionary[digit] == null)
					m_SubDictionary[digit] = new DigitsDictionary();
				return m_SubDictionary[digit].TryAdd(digits, digitIndex + 1, index, out currentIndex);
			}
		}
		public bool TryAdd(byte[] digits, int index, out int currentIndex) { return TryAdd(digits, 0, index, out currentIndex); }
	}
}

